#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "Cube.h"
#include "Processor.h"

#define MEM_WORK (1024 * 128)
#define MAX_LOOPS 5000
#define DEBUG(...) ;//printf(__VA_ARGS__)

char *memWork;

void printHex(char *mem, int len);

void *s_malloc(size_t size) {

    void *mem = malloc(size);
    if (!mem) exit(1);
    return mem;
}

void processorInit() {

    memWork = s_malloc(MEM_WORK);
}

void processorReset() {

    memset(memWork, 0, MEM_WORK);
}

void processorFree() {

    free(memWork);
}

void processorRun() {

    //Warp -get
    #define wget(p, r, s, l) ((p - s + l + r) % l + s)
    //Mem -get
    #define mget(r) *wget(wp, r, memWork, MEM_WORK)

    char *cp = memCode;
    char *wp = memWork;

    char *memCodeEnd = memCode + MEM_CODE;

    int loopCount;
    for (loopCount = 0; loopCount < MAX_LOOPS; loopCount++) {

        DEBUG("\n");

        // Code to simulate the turing machine.
        switch (*cp) {


            //========================================================//
            //  Code:
            //========================================================//

            case 'S': //Set value              (value)
                DEBUG("C Set value\n");
                cp++;
                *wp = *cp;
                break;

            case 'I': //Increase value         (value)
                DEBUG("C Increase value\n");
                cp++;
                *wp += *cp;
                break;

            case 'D': //Decrease value         (value)
                DEBUG("C Decrease value\n");
                cp++;
                *wp -= *cp;
                break;

            case 'R': {//Read                   (sticker: max=53) (Modulo 54)
                DEBUG("C Read\n");
                cp++;
                int sticker = ((unsigned char) *cp) % 54;
                *wp = cube[sticker / 9][sticker / 3 % 3][sticker % 3];
                break;
            }

            case 'W': {//Write                  (side and direction) (Mod 12)
                DEBUG("C Write\n");
                cp++;
                int op = ((unsigned char) *cp) % 12;
                if (op < 6)
                    turnClockwise(op);
                else
                    turnCounterClockwise(op - 6);
                break;
            }

            case 'J': //Jump                   (rel. addr.)
                DEBUG("C Jump\n");
                cp++;
                cp = wget(cp, *cp * 2, memCode, MEM_CODE);
                break;

            case 'E': //Jump if null           (rel. addr.)
                DEBUG("C Jump if null\n");
                cp++;
                if (!*wp) cp = wget(cp, *cp * 2, memCode, MEM_CODE);
                break;

            case 'N': //Jump if not null       (rel. addr.)
                DEBUG("C Jump if not null\n");
                cp++;
                if (*wp) cp = wget(cp, *cp * 2, memCode, MEM_CODE);
                break;


            //========================================================//
            //  Work:
            //========================================================//

            case 's': //Set value              (value)
                DEBUG("W Set value\n");
                cp++;
                *wp = mget(*cp);
                break;

            case 'i': //Increase value         (value)
                DEBUG("W Increase value\n");
                cp++;
                *wp += mget(*cp);
                break;

            case 'd': //Decrease value         (value)
                DEBUG("W Decrease value\n");
                cp++;
                *wp -= mget(*cp);
                break;

            case 'r': {//Read                   (sticker: max=53) (Modulo 54)
                DEBUG("W Read\n");
                cp++;
                int sticker = ((unsigned char) mget(*cp)) % 54;
                *wp = cube[sticker / 9][sticker / 3 % 3][sticker % 3];
                break;
            }

            case 'w': {//Write                  (side and direction) (Mod 12)
                DEBUG("W Write\n");
                cp++;
                int op = ((unsigned char) mget(*cp)) % 12;
                if (op < 6)
                    turnClockwise(op);
                else
                    turnCounterClockwise(op - 6);
                break;
            }

            case 'j': //Jump                   (rel. addr.)
                cp++;
                wp = wget(wp, *cp, memWork, MEM_WORK);
                DEBUG("W Jump\n");
                break;

            case 'e': //Jump if null           (rel. addr.)
                DEBUG("W Jump if null\n");
                cp++;
                if (!*wp) wp = wget(wp, *cp, memWork, MEM_WORK);
                break;

            case 'n': //Jump if not null       (rel. addr.)
                DEBUG("W Jump if not null\n");
                cp++;
                if (*wp) wp = wget(wp, *cp, memWork, MEM_WORK);
                break;


            //========================================================//
            //  Other:
            //========================================================//

            case 'Q': //Quit
                DEBUG("Quit\n");
                return;

            default:  //Unknown bytecode
                DEBUG("NOP\n");
                cp++;
                break;
        }

        if (++cp >= memCodeEnd) {
            DEBUG("Quit (End of code)\n");
            DEBUG("cp = %d; wp = %d\n", cp - memCode, wp - memWork);
            //printHex(memWork, 64);
            return;
        }

        DEBUG("cp = %d; wp = %d\n", cp - memCode, wp - memWork);
        //printHex(memWork, 64);
    }

    DEBUG("Quit (Run time)\n");
}

void printHex(char *mem, int len) {

    char t[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
                  '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    int i;
    for (i = 0; i < len; i++) {
        printf("%c%c ", t[mem[i] / 16], t[mem[i] % 16]);
        if (i % 16 == 15) printf("\n");
    }
}

